﻿using System;
using System.Windows;
using System.Windows.Controls;  // Button, TextBox, SelectionChangedEventArgs
using System.Windows.Input;  // KeyEventArgs, ExecutedRoutedEventArgs, RoutedUICommand
using System.Windows.Media;  // FontFamily
using System.Globalization;  // CultureInfo
using System.Windows.Threading;  // DispatcherTimer
using System.Diagnostics;  // Debug
using System.Windows.Documents;  // EditingCommands
using JHLib;
using JHVirtualKeyboard.KeyAssignmentSets;


// Notes on using this:
//
// To have the virtual keyboard (VK) remember whether it was open for your Window when it last closed,
// and automatically open with your Window when you next open it:
//
// Add an entry within your application's Settings to store this setting, in UserScope and of type bool.
// In this example, the name of the entry is "IsVirtualKeyboardUpForItemEditDialog".
//
// Handle the ContentRendered event thus:
//
//   void OnContentRendered(object sender, EventArgs e)
//   {
//       if (Properties.Settings.Default.IsVirtualKeyboardUpForItemEditDialog)
//       {
//           VirtualKeyboard.ShowOrAttachTo(this, ref _virtualKeyboard);
//       }
//   }
//
//
// Handle the Closing event thus:
//
//   void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
//   {
//       // If the virtual keyboard was up, then remember that so we can bring it up next time this same dialog-window is opened.
//       bool virtualKeyboardIsUp = _virtualKeyboard != null && _virtualKeyboard.IsUp;
//       Properties.Settings.Default.IsVirtualKeyboardUpForItemEditDialog = virtualKeyboardIsUp;
//       Properties.Settings.Default.Save();
//   }
//
// That needs to be in Closing, not Closed, because it needs to happen before the VK itself closes,
// which it automatically does if your Window is opened as a modal dialog.

namespace JHVirtualKeyboard
{
    public enum WhichKeyboardLayout { Unknown, English, Arabic, French, GermanAustrian, Italian, Mandarin, Russian, Spanish };

    #region class VirtualKeyboard
    /// <summary>
    /// Interaction logic for the Virtual Keyboard.
    /// </summary>
    public partial class VirtualKeyboard : Window
    {
        #region Constructor

        private VirtualKeyboard()
        {
            try
            {
                _isSensitive = false;
                _domain = new KeyboardViewModel();
                InitializeComponent();
                LayoutUpdated += OnLayoutUpdated;
                Loaded += OnLoaded;
                ContentRendered += OnContentRendered;
                LocationChanged += OnLocationChanged;
                Closing += new System.ComponentModel.CancelEventHandler(OnClosing);
                Closed += OnClosed;
            }
            catch (Exception x)
            {
                Console.WriteLine("Exception in VirtualKeyboard ctor: " + x.Message);
                // Below, is what I normally use as my standard for raising objections within library routines (using my own std MessageBox substitute).
                //IApp app = Application.Current as IApp;
                //if (app != null)
                //{
                //    app.TheInterlocution.NotifyUserOfError("Well this is embarrassing!", "in VirtualKeyboard.Ctor", x, this);
                //}
            }
        }
        #endregion

        #region Event Handlers

        #region LayoutUpdated event handler
        /// <summary>
        /// We use this flag to remember whether we've already done the alignment, as we only want to do it once.
        /// </summary>
        private bool _HasBeenSelfPositioned;

        /// <summary>
        /// Handle the LayoutUpdated event.
        /// We choose this event to do the alignment of this Window, because by doing so we avoid the brief flash
        /// of this Window being rendered and then moved.
        /// </summary>
        void OnLayoutUpdated(object sender, EventArgs e)
        {
            if (!_HasBeenSelfPositioned)
            {
                // Try to align it along the right side of the main window.
                this.AlignToParent(true);
                _HasBeenSelfPositioned = true;
            }
        }
        #endregion

        #region Loaded event handler
        /// <summary>
        /// Handle the Loaded event by completing the initialization of this Window.
        /// </summary>
        void OnLoaded(object sender, RoutedEventArgs e)
        {
            try
            {
                // If TargetWindow hasn't been set yet, then default it to the owning Window.
                if (_targetWindow == null)
                {
                    _targetWindow = Owner as IVirtualKeyboardInjectable;
                }

                //TODO: Also need to capture the keystrokes for the owning window,
                // as I'd like to re-map certain of those keypresses.

                // Initialize our arrays of Key Buttons..
                _keyboardButtons = new Button[] {
                  btnVK_OEM_3, btnVK_1, btnVK_2, btnVK_3, btnVK_4, btnVK_5, btnVK_6, btnVK_7, btnVK_8, btnVK_9, btnVK_0, btnVK_OEM_MINUS, btnVK_OEM_PLUS,
                  btnVK_Q, btnVK_W, btnVK_E, btnVK_R, btnVK_T, btnVK_Y, btnVK_U, btnVK_I, btnVK_O, btnVK_P, btnVK_OEM_4, btnVK_OEM_6, btnVK_OEM_5,
                  btnVK_A, btnVK_S, btnVK_D, btnVK_F, btnVK_G, btnVK_H, btnVK_J, btnVK_K, btnVK_L, btnVK_OEM_1, btnVK_OEM_7,
                  btnVK_Z, btnVK_X, btnVK_C, btnVK_V, btnVK_B, btnVK_N, btnVK_M, btnVK_OEM_COMMA, btnVK_OEM_PERIOD, btnVK_OEM_2 };

                // Could also do this in Xaml.
                int n = _keyboardButtons.Length;
                for (int i = 0; i < n; i++)
                {
                    Button button = _keyboardButtons[i];
                    button.Command = KeyPressedCommand;
                    KeyViewModel keyViewModel = _domain.TheKeyViewModels[i];
                    button.CommandParameter = keyViewModel;
                    keyViewModel.Button = button;
                }
                btnTab.Command = KeyPressedCommand;
                btnTab.CommandParameter = _domain.TabKey;
                btnVK_SPACE.Command = KeyPressedCommand;
                btnVK_SPACE.CommandParameter = _domain.VK_SPACE;
                btnEnter.Command = KeyPressedCommand;
                btnEnter.CommandParameter = _domain.EnterKey;

                // Pre-select the language.
                //Console.WriteLine("in VirtualKeyboard.OnLoaded, _lastKeyboardLayoutWasSetTo is " + _lastKeyboardLayoutWasSetTo);
                WhichKeyboardLayout lastKeyboardLayout = WhichKeyboardLayout.English;
                if (_appSettings != null)
                {
                    string sLastLanguage = _appSettings["VKLayout"] as String;
                    //Console.WriteLine("  and _appSettings indicates " + sLastLanguage);
                    Enum.TryParse(sLastLanguage, out lastKeyboardLayout);
                }
                _domain.SelectedKeyboardLayout = lastKeyboardLayout;
                SetKeyAssignmentsTo(lastKeyboardLayout);

                DataContext = _domain;
            }
            catch (Exception x)
            {
                Console.WriteLine("Exception in VirtualKeyboard.OnLoaded: " + x.Message);
                // Below, is what I normally use as my standard for raising objections within library routines (using my own std MessageBox substitute).
                //IApp app = Application.Current as IApp;
                //if (app != null)
                //{
                //    app.TheInterlocution.NotifyUserOfError("Well this is embarrassing!", "in VirtualKeyboard.OnLoaded", x, this);
                //}
            }
        }
        #endregion OnLoaded

        #region ContentRendered event handler
        /// <summary>
        /// Handle the ContentRendered event. I had to use this because the cbLanguage was getting a SelectionChanged event (to English)
        /// after the Loaded event.
        /// </summary>
        void OnContentRendered(object sender, EventArgs e)
        {
            _isSensitive = true;
            _isKeyboardLayoutChanged = false;
        }
        #endregion

        #region LocationChanged event handler
        /// <summary>
        /// Use the LocationChanged event to remember where I am relative to the Owner window.
        /// </summary>
        void OnLocationChanged(object sender, EventArgs e)
        {
            if (Owner != null && !_isIgnoringLocationChangedEvent)
            {
                _relativeXDisplacement = this.Left - Owner.Left;
                _relativeYDisplacement = this.Top - Owner.Top;
            }
        }
        #endregion

        #region cbKeyboardLayout_SelectionChanged
        /// <summary>
        /// Handle the event that ocurrs when the user selects something within the Language ComboBox.
        /// </summary>
        private void cbKeyboardLayout_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //Console.WriteLine("enter VirtualKeyboard.cbKeyboardLayout_SelectionChanged,");
            if (_isSensitive)
            {
                if (e.AddedItems.Count > 0 && e.AddedItems[0] != null)
                {
                    if (_domain.SelectedKeyboardLayout != WhichKeyboardLayout.Unknown)
                    {
                        SetKeyAssignmentsTo(_domain.SelectedKeyboardLayout);
                    }
                    _isKeyboardLayoutChanged = true;
                }
            }
            SetFocusOnTarget();
        }
        #endregion

        #region btnCapsLock_Click
        /// <summary>
        /// Handle the event that happens when the user presses the CAPS-LOCK key.
        /// </summary>
        private void btnCapsLock_Click(object sender, RoutedEventArgs e)
        {
            ToggleCapsLock();
        }
        #endregion

        #region btnShift_Click
        /// <summary>
        /// Handle the event that corresponds to the user pressing the "Shift" key.
        /// </summary>
        private void btnShift_Click(object sender, RoutedEventArgs e)
        {
            PutIntoShiftState();
        }
        #endregion

        #region OnResetTimerTick
        /// <summary>
        /// Handle the reset-timer's "Tick" event.
        /// </summary>
        private void OnResetTimerTick(object sender, EventArgs args)
        {
            ClearTimer();
            // Clear the Shift-Lock state.
            if (_domain.IsShiftLock)
            {
                _domain.IsShiftLock = false;
            }
        }
        #endregion

        #region btnBackspace_Click
        /// <summary>
        /// Handle the event that happens when the user presses the BackSpace key.
        /// </summary>
        private void btnBackspace_Click(object sender, RoutedEventArgs e)
        {
            DoBackspace();
        }
        #endregion

        #region btnDismiss_Click
        /// <summary>
        /// Handler for when the user clicks on the Dismiss This button.
        /// </summary>
        private void btnDismiss_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
        #endregion

        #region Closing event handler
        /// <summary>
        /// Handle the Closing event.
        /// </summary>
        void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (TargetWindow != null)
            {
                //TODO: This needs to be expanded to work with more than just the TextBox.
                TextBox txtTarget = TargetWindow.ControlToInjectInto as TextBox;
                if (txtTarget != null)
                {
                    if (txtTarget.CommandBindings.Count > 0)
                    {
                        if (_backspaceCommandBinding != null)
                        {
                            txtTarget.CommandBindings.Remove(_backspaceCommandBinding);
                            _backspaceCommandBinding = null;
                        }
                    }
                }
            }
        }
        #endregion

        #region Closed event handler
        /// <summary>
        /// Handle the Closed event
        /// </summary>
        void OnClosed(object sender, EventArgs e)
        {
            //Console.WriteLine("VirtualKeyboard.OnClosed, VirtualKeyboard.LastKeyboardLayoutWasSetTo = " + VirtualKeyboard.LastKeyboardLayoutWasSetTo.ToString());
            ClearTimer();
            // Remembered which language it was set to last time, so that we can pre-set it to that next time around.
            if (_appSettings != null)
            {
                if (_isKeyboardLayoutChanged)
                {
                    //Console.WriteLine("  Writing that to setting VKLayout.");
                    _appSettings["VKLayout"] = VirtualKeyboard.LastKeyboardLayoutWasSetTo.ToString();
                    _appSettings.Save();
                }
            }

            // Set the static object to null so that we know that this has closed
            // and would need to be recreated the next time.
            VirtualKeyboard.s_TheVirtualKeyboard = null;
        }
        #endregion

        #region OnTheKeyboardClosed (static method)
        /// <summary>
        /// Handle the static Closed event of the virtual keyboard - for when I close it in order to re-open it for a different dialog-window
        /// (necessary for modal dialogs).
        /// </summary>
        private static void OnTheKeyboardClosed(object sender, EventArgs e)
        {
            s_TheVirtualKeyboard = ShowIt(s_desiredTargetWindow);
        }
        #endregion

        #endregion Event Handlers

        #region ExecuteKeyPressedCommand
        /// <summary>
        /// Handle the ExecuteKeyPressed command.
        /// </summary>
        /// <param name="sender">The Window that this originated from</param>
        /// <param name="args">The Parameter property of args is the Button that issued this command</param>
        private void ExecuteKeyPressedCommand(object sender, ExecutedRoutedEventArgs args)
        {
            KeyViewModel vm = args.Parameter as KeyViewModel;
            if (vm != null)
            {
                //Console.WriteLine("ExecuteKeyPressedCommand, btn.Name = " + btn.Name);
                Inject(vm);
            }
            else
            {
                String sWhat = args.Parameter as String;
                if (sWhat != null && sWhat.Length > 0)
                {
                    Inject(sWhat);
                }
            }
            args.Handled = true;
        }
        #endregion

        #region Methods

        #region The
        /// <summary>
        /// Get the (only) VirtualKeyboard instance.
        /// </summary>
        public static VirtualKeyboard The
        {
            get { return s_TheVirtualKeyboard; }
        }
        #endregion

        #region ShowOrAttachTo - this is what you call from your own application.
        /// <summary>
        /// Either create and show, or else re-attach to (if it's already up) the virtual keyboard.
        /// To be called from the desired target-Window that wants to use it.
        /// </summary>
        /// <param name="targetWindow">The Window that wants to be the owner of the virtual-keyboard Window and the target of it's output</param>
        /// <param name="myPointerToIt">The targetWindow's own instance-variable that will point to the virtual keyboard object</param>
        public static void ShowOrAttachTo(IVirtualKeyboardInjectable targetWindow, ref VirtualKeyboard myPointerToIt)
        {
            try
            {
                s_desiredTargetWindow = targetWindow;
                // Evidently, for modal Windows I can't share user-focus with another Window unless I first close and then recreate it.
                // A shame. Seems like a waste of time. But I don't know of a work-around to it (yet).
                if (IsUp)
                {
                    Console.WriteLine("VirtualKeyboard: re-attaching to a different Window.");
                    VirtualKeyboard.The.Closed += new EventHandler(OnTheKeyboardClosed);
                    VirtualKeyboard.The.Close();
                    myPointerToIt = null;
                }
                else
                {
                    myPointerToIt = ShowIt(targetWindow);
                }
            }
            catch (Exception x)
            {
                Console.WriteLine("Exception in VirtualKeyboard.ShowOrAttachTo: " + x.Message);
                // Below, is what I normally use as my standard for raising objections within library routines (using my own std MessageBox substitute).
                //IInterlocution inter = Application.Current as IInterlocution;
                //if (inter != null)
                //{
                //    inter.NotifyUserOfError("Well, now this is embarrassing.", "in VirtualKeyboard.ShowOrAttachTo.", x);
                //}
            }
        }
        #endregion

        #region ShowIt  - this is the factory method for this class
        /// <summary>
        /// Create (and return) the VirtualKeyboard, and Show it.
        /// This is the factory-method for instantiating and showing the virtual keyboard.
        /// </summary>
        /// <param name="parentWindow">The window that will accept input from the keyboard</param>
        /// <returns>a reference to the VirtualKeyboard</returns>
        private static VirtualKeyboard ShowIt(IVirtualKeyboardInjectable targetWindow)
        {
            if (s_TheVirtualKeyboard == null)
            {
                s_TheVirtualKeyboard = new VirtualKeyboard();
            }
            s_TheVirtualKeyboard.Owner = (Window)targetWindow;
            s_TheVirtualKeyboard.ShowActivated = false;
            if (!s_TheVirtualKeyboard.IsLoaded)
            {
                s_TheVirtualKeyboard.Show();
            }
            s_TheVirtualKeyboard.TargetWindow = targetWindow;
            targetWindow.ControlToInjectInto.Focus();
            return s_TheVirtualKeyboard;
        }
        #endregion

        #region Movement as a group

        public void MoveAlongWith()
        {
            this.MoveAsAGroup(_relativeXDisplacement, _relativeYDisplacement, ref _isIgnoringLocationChangedEvent);
        }
        #endregion

        #region Inject
        /// <summary>
        /// Type the given string into whatever the target TextBox (or similar control) is.
        /// </summary>
        /// <param name="sWhat"></param>
        protected void Inject(string sWhat)
        {
            if (TargetWindow != null)
            {
                ((Window)TargetWindow).Focus();
                TextBox txtTarget = TargetWindow.ControlToInjectInto as TextBox;
                if (txtTarget != null)
                {
                    txtTarget.InsertText(sWhat);
                }
                else
                {
                    RichTextBox richTextBox = TargetWindow.ControlToInjectInto as RichTextBox;
                    if (richTextBox != null)
                    {
                        richTextBox.InsertText(sWhat);
                    }
                    else // let's hope it's an IInjectableControl
                    {
                        IInjectableControl targetControl = TargetWindow.ControlToInjectInto as IInjectableControl;
                        if (targetControl != null)
                        {
                            targetControl.InsertText(sWhat);
                        }
                    }
                    //else   // if you have other text-entry controls such as a rich-text box, include them here.
                //{
                //    FsWpfControls.FsRichTextBox.FsRichTextBox txtrTarget = TargetWindow.ControlToInjectInto as FsWpfControls.FsRichTextBox.FsRichTextBox;
                //    if (txtrTarget != null)
                //    {
                //        txtrTarget.InsertThis(sWhat);
                //    }
                }
            }
        }

        /// <summary>
        /// Inject the character represented by the given "key" switch view-model, into whatever the target TextBox is.
        /// </summary>
        /// <param name="vm">The KeyViewModel that carries the information of what to inject</param>
        public void Inject(KeyViewModel vm)
        {
            // The Tag would've been another way to associate the view-model with the button.
            if (vm != null)
            {
                if (vm == _domain.EnterKey)
                {
                    Inject(Environment.NewLine);
                }
                else
                {
                    Inject(vm.CodePoint.ToString());
                }
            }
            // If we were in Shift-mode, reset that now.
            _domain.IsShiftLock = false;
            ClearTimer();
        }
        #endregion

        #region DoBackspace
        /// <summary>
        /// Simulate the press of the Backspace key.
        /// </summary>
        public void DoBackspace()
        {
            if (TargetWindow != null)
            {
                ((Window)TargetWindow).Focus();
                TextBox txtTarget = TargetWindow.ControlToInjectInto as TextBox;
                if (txtTarget != null)
                {
                    // Use the built-in Backspace command. Create it only once, and remove it when this VirtualKeyboard window is closing.
                    if (_backspaceCommandBinding == null)
                    {
                        _backspaceCommandBinding = new CommandBinding(EditingCommands.Backspace);
                        txtTarget.CommandBindings.Add(_backspaceCommandBinding);
                    }
                    _backspaceCommandBinding.Command.Execute(null);

                    // This was how I did the Backspace operation without using the command..
                    //int iCaretIndex = txtTarget.CaretIndex;
                    //if (iCaretIndex > 0)
                    //{
                    //    string sOriginalContent = txtTarget.Text;
                    //    int nLength = sOriginalContent.Length;
                    //    if (iCaretIndex >= nLength)
                    //    {
                    //        txtTarget.Text = sOriginalContent.Substring(0, nLength - 1);
                    //    }
                    //    else
                    //    {
                    //        string sPartBeforeCaret = sOriginalContent.Substring(0, iCaretIndex - 1);
                    //        string sPartAfterCaret = sOriginalContent.Substring(iCaretIndex, nLength - iCaretIndex);
                    //        txtTarget.Text = sPartBeforeCaret + sPartAfterCaret;
                    //    }
                    //    txtTarget.CaretIndex = iCaretIndex - 1;
                    //}
                }
                else // let's hope it's an IInjectableControl
                {
                    IInjectableControl targetControl = TargetWindow.ControlToInjectInto as IInjectableControl;
                    if (targetControl != null)
                    {
                        targetControl.Backspace();
                    }
                    //else
                    //{
                    //    FsWpfControls.FsRichTextBox.FsRichTextBox txtrTarget = TargetWindow.ControlToInjectInto as FsWpfControls.FsRichTextBox.FsRichTextBox;
                    //    if (txtrTarget != null)
                    //    {
                    //        txtrTarget.InsertThis(chWhat.ToString());
                    //    }
                }
            }
            //textbox.Text = sOriginalContent.Insert(iCaretIndex, sStuffToInsert);
        }
        #endregion

        #region SetCulture
        /// <summary>
        /// Set the language of the GUI itself (as distinct from the setting of the keyboard layout).
        /// </summary>
        /// <param name="culture"></param>
        public void SetCulture(CultureInfo culture)
        {
            //TODO  This is just tinkering at this point -- it all needs to be implemented.
            string sCode = culture.TwoLetterISOLanguageName;
            string sName = culture.Name;
            int iKeyboardLayoutId = culture.KeyboardLayoutId;
            //if (sCode == "de")  // German
            //{
            //    btnEnter.Content = "?";
            //    btn
            //}
            if (sCode == "es")  // Spanish
            {
                btnEnter.Content = "Intro";
                btnVK_SPACE.Content = "Spacio";
            }
            else if (sCode == "fr")  // French
            {
                btnEnter.Content = "Entree";
                btnVK_SPACE.Content = "Space";
                btnCapsLock.Content = "Verr Maj";
            }
            else if (sCode == "ru")  // Russian
            {
                btnEnter.Content = "Enter";
                btnVK_SPACE.Content = "Mecto";
            }
            else  // default to English
            {
                btnEnter.Content = "Enter";
                btnVK_SPACE.Content = "Space";
            }
        }
        #endregion

        #region SetKeyAssignmentsTo
        /// <summary>
        /// Set the key assignments to those of the give language.
        /// </summary>
        public void SetKeyAssignmentsTo(WhichKeyboardLayout whichKeyboardLayout)
        {
            //Console.WriteLine("in SetKeyAssignmentsTo(" + whichKeyboardLayout.ToString() + "), ");
            switch (whichKeyboardLayout)
            {
                case WhichKeyboardLayout.Russian:
                    //TODO: Might we possibly need to assign different fonts within the same set?
                    //string sFontFamilyForSha = "Aboriginal Serif";
                    //string sFontFamilyForSha = "Athena Unicode";
                    //btnVK_I.FontFamily = new FontFamily(sFontFamilyForSha);
                    //btnVK_O.FontFamily = new FontFamily(sFontFamilyForSha);
                    break;
                default: // default to English
                    break;
            }
            AssignKeys(KeyAssignmentSet.For(whichKeyboardLayout));
        }
        #endregion SetKeyAssignmentsTo

        #region AssignKeys
        /// <summary>
        /// Modify the actual keyboard keys (which are WPF Buttons) based upon the given KeyAssignmentSet
        /// (ie, change the actual appearance of the Buttons).
        /// </summary>
        /// <param name="keyAssignmentSet">What to assign to the keys</param>
        public void AssignKeys(KeyAssignmentSet keyAssignmentSet)
        {
            //Console.Write("AssignKeys(" + keyAssignmentSet.Language + "), ");
            int n = _domain.TheKeyViewModels.Length;
            Debug.Assert(n == keyAssignmentSet.KeyAssignments.Length, "These collections should be the same length.");
            for (int i = 0; i < n; i++)
            {
                KeyViewModel vm = _domain.TheKeyViewModels[i];
                KeyAssignment ka = keyAssignmentSet.KeyAssignments[i];
                vm.IsLetter = ka.IsLetter;
                vm.UnshiftedCodePoint = (char)ka.UnshiftedCodePoint;
                vm.ShiftedCodePoint = (char)ka.ShiftedCodePoint;
                if (ka.IsToShowBothGlyphs)
                {
                    // If it's an underscore,
                    if (ka.ShiftedCodePoint == 0x005F)
                    {
                        // double it up because the first one gets eaten
                        string sText = (char)ka.ShiftedCodePoint + Environment.NewLine + (char)ka.UnshiftedCodePoint;
                        string sTextWithUnderscoresDoubled = sText.Replace("_", @"__");
                        vm.Text = sTextWithUnderscoresDoubled;
                    }
                    else
                    {
                        vm.Text = (char)ka.ShiftedCodePoint + Environment.NewLine + (char)ka.UnshiftedCodePoint;
                    }
                }
                else
                {
                    vm.Text = null;
                }
                vm.UnshiftedToolTip = ka.UnshiftedToolTip;
                vm.ShiftedToolTip = ka.ShiftedToolTip;
                // Assign the font family, if one is specified..
                if (!String.IsNullOrEmpty(ka.FontFamilyName))
                {
                    // Those assigned to this specific key-assignment, override those assigned to the key-assignment-set.
                    vm.FontFamily = new FontFamily(ka.FontFamilyName);
                }
                else
                {
                    vm.FontFamily = null;
                }
            }
        }
        #endregion

        #region ToggleCapsLock
        /// <summary>
        /// Flip the state of the CAPS-LOCK key-button.
        /// </summary>
        public void ToggleCapsLock()
        {
            if (_domain.IsCapsLock)
            {
                _domain.IsCapsLock = false;
            }
            else
            {
                _domain.IsCapsLock = true;
            }
            SetFocusOnTarget();
        }
        #endregion

        #region PutIntoShiftState
        /// <summary>
        /// Flip the state of the SHIFT key-button into the shifted state.
        /// </summary>
        public void PutIntoShiftState()
        {
            // Toggle the shift-lock state.
            _domain.IsShiftLock = !_domain.IsShiftLock;
            // If we're turning Shiftlock on, give that a 10-second timeout before it resets by itself.
            if (_domain.IsShiftLock)
            {
                ClearTimer();
                _resetTimer = new DispatcherTimer(TimeSpan.FromSeconds(10),
                                                  DispatcherPriority.ApplicationIdle,
                                                  new EventHandler(OnResetTimerTick),
                                                  this.Dispatcher);
            }
            SetFocusOnTarget();
        }
        #endregion

        #region SetFocusOnTarget
        /// <summary>
        /// Set focus to the TextBox (or whatever we're injecting keystrokes into) of the target window.
        /// </summary>
        public void SetFocusOnTarget()
        {
            if (TargetWindow != null)
            {
                ((Window)TargetWindow).Focus();
            }
        }
        #endregion

        #region ClearTimer
        /// <summary>
        /// Reset the dispose of the timer that's used to reset the shift-lock state back to the unshifted state.
        /// </summary>
        private void ClearTimer()
        {
            // Dispose of the reset-timer, if that's still around.
            if (_resetTimer != null)
            {
                if (_resetTimer.IsEnabled)
                {
                    _resetTimer.Stop();
                }
                _resetTimer = null;
            }
        }
        #endregion

        #endregion Methods

        #region Properties

        #region TargetWindow
        /// <summary>
        /// Get/set which Window we're going to inject characters into.
        /// </summary>
        public IVirtualKeyboardInjectable TargetWindow
        {
            get { return _targetWindow; }
            set { _targetWindow = value; }
        }
        #endregion

        #region IsUp
        /// <summary>
        /// Get whether this virtual keyboard is currently showing,
        /// as indicated by whether the class-variable has been set to null.
        /// </summary>
        public static bool IsUp
        {
            get { return s_TheVirtualKeyboard != null; }
        }
        #endregion

        #region SelectedKeyboardLayout
        /// <summary>
        /// Get which language this virtual keyboard is currently set to.
        /// </summary>
        public WhichKeyboardLayout SelectedKeyboardLayout
        {
            get { return _domain.SelectedKeyboardLayout; }
        }
        #endregion

        #region LastKeyboardLayoutWasSetTo
        /// <summary>
        /// Get the most recent setting for this keyboard, so that it may be pre-selected next time.
        /// </summary>
        public static WhichKeyboardLayout LastKeyboardLayoutWasSetTo
        {
            get { return _lastKeyboardLayoutWasSetTo; }
            set { _lastKeyboardLayoutWasSetTo = value; }
        }
        #endregion

        #region SaveStateToMySettings
        /// <summary>
        /// Your Window should call this before showing the virtual keyboard, if you want it to remember which language it was set to.
        /// </summary>
        /// <param name="appSettings">Your application's Settings object</param>
        public static void SaveStateToMySettings(System.Configuration.ApplicationSettingsBase appSettings)
        {
            _appSettings = appSettings;
        }
        #endregion

        #endregion Properties

        #region The RoutedUICommands

        public static RoutedUICommand KeyPressedCommand = new RoutedUICommand();

        #endregion The RoutedUICommands

        #region Fields

        private static VirtualKeyboard s_TheVirtualKeyboard;
        private static IVirtualKeyboardInjectable s_desiredTargetWindow;
        private IVirtualKeyboardInjectable _targetWindow;
        private KeyboardViewModel _domain;
        private Button[] _keyboardButtons;
        private double _relativeXDisplacement;
        private double _relativeYDisplacement;
        private bool _isIgnoringLocationChangedEvent;
        private DispatcherTimer _resetTimer;
        private static WhichKeyboardLayout _lastKeyboardLayoutWasSetTo;
        private static System.Configuration.ApplicationSettingsBase _appSettings;
        private bool _isSensitive;
        private bool _isKeyboardLayoutChanged;
        private CommandBinding _backspaceCommandBinding;

        #endregion Fields
    }
    #endregion class VirtualKeyboard
}
